#!/usr/bin/python3
import sys
import os
import logsetup
import logging
import multiprocessing
import subprocess
import traceback

__HELP__ = """
ocean [flags] build @target
ocean [flags] run @target [-- args to target]
ocean [flags] test @target

flags is one of the following
  --variant=list,of,variants

@target is specified like so:
 - //path/to/file:target_name
 - rel_path/to/file:target_name
 - :target_name
 - //recursive/path/...
"""

import repo_path
import config
import watch_server.server_lock
import graph.run
import graph.invalidate
import graph.clean
import build_file.graph
import u_complete_me
import dep_graph.cache

def main():
  if u_complete_me.check_for_auto_complete():
    exit(0)

  logger = logsetup.init()
  # TODO(aschuh): Flag for setting the log level.
  # Only log DEBUG if VLOG is on, and then add a vlog
  logsetup.setLogLevel(logging.WARNING)

  build_variants = None
  args = []
  for number, arg in enumerate(sys.argv[1:]):
    if arg == '--':
      args += sys.argv[2 + number:]
      break

    if arg[0] != '-':
      args.append(arg)
      continue

    if arg[:10] == '--variant=':
      build_variants = arg[10:].split(',')
    elif arg == '-v':
      logsetup.setLogLevel(logging.INFO)
    elif arg == '-vv':
      logsetup.setLogLevel(logging.DEBUG)
    else:
      print('Unrecognized argument', arg)
      print(__HELP__)
      exit()

  try:
    if len(args) < 2:
      print(args)
      print(__HELP__)
      exit()
    if args[0] == 'run':
      extra_args = args[2:]
      args = args[:2]
    my_config = config.RepoConfig(__file__)
    my_config.load_config_filelist()

    if build_variants is None:
      try:
        if args[0] == 'run':
          build_variants = my_config.run_variants()
        else:
          build_variants = my_config.default_variants()
      except config.InvalidVariantException as err:
        print('Error: ' + err.name + ' in the config must be a list of strings.')
        exit(1)

    if len(build_variants) > 1:
      print('Building variants', ', '.join(build_variants))
    else:
      print('Building variant', build_variants[0])

    variants = my_config.variants()
    for variant in build_variants:
      if variant not in variants:
        print('Error: Variant', variant, 'not in the available set of variants')
        print('Must be one of', ', '.join(build_variants))
        exit(1)

    with watch_server.server_lock.getClient(my_config) as watcher:
      clean_graph = False
      if watcher.just_started or args[0] == 'build_clean':
        clean_graph = True

      eval_graph = graph.run.EvalGraph()
      affected = set(watcher.get_affected())
      logger.info('affected files: ' + repr(affected))

      if set.intersection(affected, my_config.config_dependency_files):
        clean_graph = True

      with config.OpenGraphDatabase(clean_graph=clean_graph) as graph_cache:
        if not clean_graph:
          graph.invalidate.invalidate_file_list(graph_cache, affected)

        my_graph = build_file.graph.BuildRuleCachedGraph(my_config, graph_cache)
        watcher.ack_affected()

        all_keys = set()
        all_rules = set()
        keys = set()
        for variant in build_variants:
          variant_keys = set(my_config.names_to_keys(args[1:], variant, graph=my_graph))
          keys |= variant_keys

        for key in keys:
          all_keys.add(key.full_name())
          rule = my_graph.get_rule_with_key(key)
          eval_graph.add_final_genrule(rule)
          all_rules.add(rule)

        logging.info('Building:', repr(list(all_keys)))
        eval_graph.add_back_files.update(my_graph.addedBuildFiles())

        if args[0] == 'test':
          for rule in all_rules:
            if 'test' in rule.out:
              eval_graph.add_test(rule.out.test, rule.full_name())

        graph.clean.CleanOutputDirectory(graph_cache,
                                         my_config.bin_directory())

        print('Starting to build.')
        all_good = eval_graph.run_cmds()
        re_valid = eval_graph.get_add_back()
        logger.info("unwatch: " + repr(list(affected - re_valid)))
        watch_list = list(
            (re_valid - affected).union(my_config.config_dependency_files))
        logger.info("watch  : " + repr(watch_list))
        watcher.unwatch(list(affected - re_valid))
        watcher.watch(watch_list)
        
        if not all_good:
          exit(-1)
        if args[0] == 'build' or args[0] == 'build_clean':
          #all done.
          pass
        elif args[0] == 'run':
          run_commands = set()
          for rule in all_rules:
            for fname in rule.out.executable.file_names():
              run_commands.add(fname)

    if args[0] == 'run':
      if len(run_commands) == 1:
        print('Running', fname, ' '.join(extra_args))
        sys.stdout.flush()
        sys.stderr.flush()
        fname = list(run_commands)[0]
        fname = os.path.realpath(fname)
        os.execvp(fname, [fname] + extra_args)
      else:
        logger.fatal('Unable to run %s, only able to run 1 command', str(run_commands))
  except watch_server.client_lib.AlreadyOpenedError:
    print('Can\'t run multiple builds simultaneously.')
    exit(-1)
  except repo_path.Error as e:
    print(str(e))
    exit(-1)
  except KeyboardInterrupt as e:
    exc_type, exc_value, exc_traceback = sys.exc_info()
    print('\nExiting')
    for line in traceback.format_exception(exc_type, exc_value,
                                           exc_traceback):
      for subline in line[:-1].split('\n'):
        logger.info(subline)
    exit(-1)


if __name__ == '__main__':
  main()
